use core::ptr::copy_nonoverlapping;
use crate::{encode::ProtoEncode, frame::Proto};

#[cfg(not(feature = "address_proto"))]
pub const MIN_LEN: usize = 7;

#[cfg(feature = "address_proto")]
pub const MIN_LEN: usize = 9;

#[cfg(not(feature = "address_proto"))]
pub const MIN_CMD_LEN: usize = 8;

#[cfg(feature = "address_proto")]
pub const MIN_CMD_LEN: usize = 10;

pub const HEAD0: u8 = 0xE1;
pub const HEAD1: u8 = 0x1E;
pub const END: u8 = 0xEF;

pub fn xor_sum(buf: &[u8]) -> u8 {
    let mut s = 0;
    for v in buf {
        s = s ^ *v;
    }
    s
}

pub fn is_checked(buf: &[u8]) -> bool {
    let len = buf.len();
    if len < MIN_LEN {
        return false;
    }
    if buf[0] != HEAD0 {
        return false;
    }
    if buf[1] != HEAD1 {
        return false;
    }
    if buf[2] != (len as u8) {
        return false;
    }
    let sum = xor_sum(&buf[3..(len - 2)]);
    if sum != buf[len - 2] {
        return false;
    }
    buf[len - 1] == END
}

pub trait ToProtoBytes {
    fn to_bytes(self, write_buf: &mut [u8]) -> Option<usize>;
}

impl ToProtoBytes for &[u8] {
    fn to_bytes(self, write_buf: &mut [u8]) -> Option<usize> {
        if self.len() > write_buf.len() {
            None
        } else {
            unsafe {
                copy_nonoverlapping(
                    self.as_ptr(),
                    write_buf.as_mut_ptr(), 
                    self.len()
                );
            }
            Some(self.len())
        }
    }
}

#[cfg(not(feature = "address_proto"))]
impl<T: ProtoEncode> ToProtoBytes for Proto<T> {
    fn to_bytes(self, write_buf: &mut [u8]) -> Option<usize> {
        let len = write_buf.len();
        if len < MIN_LEN {
            None
        } else {
            write_buf[0] = HEAD0;
            write_buf[1] = HEAD1;
            //write_buf[2] = 0;
            write_buf[3] = self.ty;
            write_buf[4] = self.seq;
            let n = self.body.encode(&mut write_buf[5..(len - 2)])?;
            let s = xor_sum(&write_buf[3..(5 + n)]);
            write_buf[5 + n] = s;
            write_buf[6 + n] = END;
            write_buf[2] = (n + MIN_LEN) as u8;
            Some(n + MIN_LEN)
        }
    }
}

#[cfg(feature = "address_proto")]
impl<T: ProtoEncode> ToProtoBytes for Proto<T> {
    fn to_bytes(self, write_buf: &mut [u8]) -> Option<usize> {
        let len = write_buf.len();
        if len < MIN_LEN {
            None
        } else {
            write_buf[0] = HEAD0;
            write_buf[1] = HEAD1;
            //write_buf[2] = 0;
            write_buf[3] = self.ty;
            write_buf[4] = self.seq;
            write_buf[5] = self.src;
            write_buf[6] = self.dst;
            let n = self.body.encode(&mut write_buf[7..(len - 2)])?;
            let s = xor_sum(&write_buf[3..(7 + n)]);
            write_buf[7 + n] = s;
            write_buf[8 + n] = END;
            write_buf[2] = (n + MIN_LEN) as u8;
            Some(n + MIN_LEN)
        }
    }
}
